﻿Public Class Skyscrapers
    Inherits Puzzle

    Public Overrides Sub Draw()
        MyBase.Draw()
        For Each Group In Groups
            Dim x As Integer = If(Group.Type = 2, If(Group.Reverse, Size + 1, 0), Group.Index)
            Dim y As Integer = If(Group.Type = 1, If(Group.Reverse, Size + 1, 0), Group.Index)
            DrawGiven(CellRect(x, y), Group.Hint(0), False)
        Next
        DrawRect(1, 0, Size, 1, 1)
        DrawRect(1, Size + 1, Size, 1, 1)
        DrawRect(0, 1, 1, Size, 1)
        DrawRect(Size + 1, 1, 1, Size, 1)
    End Sub

    Public Overrides Sub NewPuzzle()
        MyBase.NewPuzzle()
        Groups = New Groups(From t In Range(1, 2) From i In Range(1, Size) From b In Range(1, 2) Let Group = New Group(b = 2, t, i, New Cells(From c In Cells Where c.Index(t) = i)) Select CType(Group, PuzzleStudio.Group))
        For Each Item In Cells
            Dim Cell = Item
            Cell.Groups.Add((From g In Groups Where g.Type = 1 And g.Index = Cell.x).First)
            Cell.Groups.Add((From g In Groups Where g.Type = 2 And g.Index = Cell.y).First)
        Next
    End Sub

    Public Overrides Function OnKeyPress(ByVal Key As String) As Boolean
        Select Case Key
            Case "0" To "9"
                Dim t As Integer = If(Cursor.Y = 0 Or Cursor.Y = Size + 1, 0, If(Cursor.X = 0 Or Cursor.X = Size + 1, 1, 2))
                Dim d As Integer = If(Cursor.Y = 0 Or Cursor.X = 0, 0, If(Cursor.Y = Size + 1 Or Cursor.X = Size + 1, 1, 2))
                If t < 2 And d < 2 Then 'And Trim(Key - Keys.D0) >= 1 And Trim(Key - Keys.D0) <= Size Then
                    Groups(If(t = 0, 0, 2 * Size) + 2 * If(t = 1, Cursor.Y - 1, Cursor.X - 1) + d).Hint(0) = Key
                    Invalidate()
                    Solve()
                End If
            Case "delete"
                Dim t As Integer = If(Cursor.Y = 0 Or Cursor.Y = Size + 1, 0, If(Cursor.X = 0 Or Cursor.X = Size + 1, 1, 2))
                Dim d As Integer = If(Cursor.Y = 0 Or Cursor.X = 0, 0, If(Cursor.Y = Size + 1 Or Cursor.X = Size + 1, 1, 2))
                If t < 2 And d < 2 Then
                    Groups(If(t = 0, 0, 2 * Size) + 2 * If(t = 1, Cursor.Y - 1, Cursor.X - 1) + d).Hint(0) = ""
                    Invalidate()
                    Solve()
                End If
        End Select
        Return MyBase.OnKeyPress(Key)
    End Function

    Public Overrides Sub Load(ByVal Data As String)
        Size = Len(Data) / 4
        NewPuzzle()
        Dim i As Integer = 1
        For Each Group In Groups
            Group.Hint(0) = Mid(Data, i, 1)
            i += 1
        Next
        Invalidate()
    End Sub

    Public Overrides Function Save() As String
        Return ""
    End Function

    Public Overrides Sub Solve()
        ClearLog()
        For Each Cell In Cells
            Cell.Reset(Mid("123456789", 1, Size))
        Next
        For Each Group In Groups
            Group.Reset()
        Next
        ProcessGivens()
        Dim Level As Integer = 1
        Dim Times(99) As Integer, Counts(99) As Integer
        Dim Watch As New Stopwatch
        Do
            Dim Progress As Boolean = False
            Watch.Reset()
            Watch.Start()
            Select Case Level
                Case 1 : Progress = NakedCandidates()
                Case 2 : Progress = SingleCandidates()
                Case 3 : Progress = NakedPairs()
                Case 4 : Progress = HiddenPairs()
                Case 5 : Progress = ProcessCombinations()
                Case 6 : Progress = NakedTriples()
                Case 7 : Progress = HiddenTriplets()
                Case 8 : Progress = XWing()
                Case Else : If Not Progress Then Exit Do
            End Select
            Watch.Stop()
            Counts(Level) += 1
            Times(Level) += Watch.ElapsedMilliseconds
            Level = IIf(Progress, 1, Level + 1)
            FlushLog()
            If (From c As Cell In Cells Where c.f = "" Select c).Count = 0 Then Exit Do
            ' Invalidate()
            'MsgBox(1)
        Loop
        Invalidate()
        Log.Append("")
        For i As Integer = 1 To 99
            If Counts(i) > 0 Then Log.Append(Choose(i, "naked candidates", "single candidates", "candidate lines", "block pairs", "naked pairs", "hidden pairs", "naked triplets", "hidden triplets", "hidden-quad", "x-wing", "swordfish", "jellyfish", "xy-wing", "xyz-wing", "coloring", "multicoloring", "xy-chain") & " : " & Counts(i) & " (" & Times(i) & "ms)")
        Next
        FlushLog()
        Invalidate()
    End Sub

    Sub ProcessGivens()
        For Each g In From gr In Groups Where gr.HasHint
            g.ProcessHints()
        Next
    End Sub

    Function ProcessCombinations() As Boolean
        Dim Progress As Boolean
        For Each g In From gr In Groups Where gr.HasHint
            Progress = Progress Or g.ProcessCombinations
        Next
        Return Progress
    End Function

    Public Sub New()
        Size = 5
        HintArea = New Rectangle(1, 1, 1, 1)
        AvailableSizes = Enumerable.Range(4, 6).ToList
    End Sub

    Public Class Group
        Inherits PuzzleStudio.Group

        Sub New(ByVal Reverse As Boolean, ByVal Type As Integer, ByVal Index As Integer, Cells As Cells)
            MyBase.New(Reverse, Type, Index, Cells)
        End Sub

        Overrides Sub ProcessHints()
            Select Case Hint(0)
                Case ""
                Case "1"
                    If Cells(0).Candidates.Count > 1 Then Cells(0).Candidates = Trim(Size).ToList
                Case Trim(Size)
                    For i As Integer = 1 To Size
                        If Cells(i - 1).Candidates.Count > 1 Then Cells(i - 1).Candidates = Trim(i).ToList
                    Next
                Case Else
                    For i = 1 To Val(Hint(0))
                        Dim Cell = Cells(i - 1)
                        Dim l = Cells.Last.f
                        For b = Size - Hint(0) + i + 1 To Size
                            If Cell.Contains(b) Then Cell.Eliminate(b)
                        Next
                    Next
            End Select
            If Hint(0) = "2" AndAlso Cells(1).Contains(Size - 1) Then Cells(1).Eliminate(Size - 1)
        End Sub

        Public Overrides Function ProcessCombinations() As Boolean
            Dim Progress As Boolean
            Dim Combinations As New List(Of String)
            GetCombinations(Combinations, "")
            For i As Integer = 0 To Size - 1
                Dim c = i
                Dim a = (From t In Combinations Select t(c)).Distinct
                For Each ch In (Cells(i).Candidates).ToList
                    If Not a.Contains(ch) Then Cells(i).Eliminate(ch) : Progress = True
                Next
            Next
            Return Progress
        End Function

        Sub GetCombinations(ByRef List As List(Of String), ByVal t As String)
            If Len(t) = Size Then
                If Count(t) = Hint(0) Then List.Add(t)
            Else
                Dim Cnd As String = Cells(Len(t)).f & Cells(Len(t)).CandidateStr
                For i = 1 To Len(Cnd)
                    If Not t.Contains(Mid(Cnd, i, 1)) Then GetCombinations(List, t & Mid(Cnd, i, 1))
                Next
            End If
        End Sub

        Function Count(ByVal t As String) As Integer
            Dim m As String = ""
            Dim c As Integer = 0
            For i = 0 To Len(t) - 1
                If t(i) > m Then m = t(i) : c += 1
            Next
            Return c
        End Function

    End Class

End Class
